import WatchValue from "@/utils/WatchValue";
import WatchNumber from "@/utils/WatchNumber";

/** HandleInfinity */
export const HF = (num: number) => isFinite(num) ? num : 0;

export type ScrollItemInfo<T> = readonly [T, boolean, readonly [number, number, number, number], readonly [number, number, number, number]];

export interface ScrollParam<T> {
	Left: number;
	Top: number;
	Width: number;
	Height: number;
	Padding: number;
	ItemWidth: number;
	ItemHeight: number;
	ItemPadding: number;
	List: T[];
	ScrollWidth: number;
}

export type ScrollNames = keyof ScrollParam<any>;

export type ScrollConfig<T> = { [name in ScrollNames]?: ScrollParam<T>[name] | WatchValue<ScrollParam<T>[name]>; }

export type ScrollToolParam<T> = { [name in ScrollNames]: WatchValue<ScrollParam<T>[name]>; }

// TODO: 需要ItemSpace
export default class ScrollTool<T> implements ScrollToolParam<T> {
	public readonly Left = new WatchNumber(0);
	public readonly Top = new WatchNumber(0);
	public readonly Width = new WatchNumber(0);
	public readonly Height = new WatchNumber(0);
	public readonly Padding = new WatchNumber(0);
	public readonly ItemWidth = new WatchNumber(0);
	public readonly ItemHeight = new WatchNumber(0);
	public readonly ItemPadding = new WatchNumber(0);
	public readonly ScrollWidth = new WatchNumber(0);
	public readonly List = new WatchValue<T[]>([]);
	public readonly Length = new WatchNumber(() => this.List.Value.length, this.List);
	public readonly Col = new WatchNumber(() =>
		Math.floor((this.Width.Value - this.Padding.Value * 2 - this.ScrollWidth.Value) / this.ItemWidth.Value)
		, this.Width, this.Padding, this.ScrollWidth, this.ItemWidth);
	public readonly Row = new WatchNumber(() => Math.floor((this.Height.Value - this.Padding.Value * 2) / this.ItemHeight.Value), this.Height, this.Padding, this.ItemHeight);
	public readonly Count = new WatchNumber(() => this.Row.Value * this.Col.Value, this.Row, this.Col);
	public readonly RowCount = new WatchNumber(() => Math.ceil(this.Length.Value / this.Col.Value), this.Length, this.Col);
	public readonly Step = new WatchNumber(() => {
		const step = this.RowCount.Value - this.Row.Value + 1;
		return step < 0 ? 0 : step;
	}, this.RowCount, this.Row);
	public readonly ScrollHeight = new WatchNumber(() => Math.ceil(this.Height.Value / this.Step.Value), this.Height, this.Step); // 以后有可能会减去Padding
	public readonly ScrollSpace = new WatchNumber(() => (this.Height.Value - this.ScrollHeight.Value) / (this.Step.Value - 1), this.Height, this.ScrollHeight, this.Step);

	public _Progress = 0;

	public get Progress() {
		return this._Progress;
	}
	public set Progress(progress) {
		const step = this.Step.Value;
		if (progress < 0) { progress = 0; }
		else if (progress >= step) { progress = step > 1 ? step - 1 : 0; }
		this._Progress = progress;
	}

	public _SelectItem: T | undefined = undefined;
	public _SelectIndex: number | undefined = undefined;

	public get SelectIndex() {
		return this._SelectIndex;
	}
	public set SelectIndex(selectIndex) {
		const list = this.List.Value;
		const length = this.Length.Value;
		if (selectIndex !== undefined && selectIndex >= 0 && selectIndex < length) {
			this._SelectIndex = selectIndex;
			this._SelectItem = list[selectIndex];
		} else {
			this._SelectIndex = undefined;
			this._SelectItem = undefined;
		}
	}

	public get SelectItem() {
		return this._SelectItem;
	}
	public set SelectItem(selectItem) {
		const list = this.List.Value;
		if (selectItem !== undefined && list.indexOf(selectItem) !== -1) {
			const selectIndex = list.indexOf(selectItem);
			if (selectIndex !== -1) {
				this._SelectIndex = selectIndex;
				this._SelectItem = selectItem;
			} else {
				this._SelectIndex = undefined;
				this._SelectItem = undefined;
			}
		} else {
			this._SelectIndex = undefined;
			this._SelectItem = undefined;
		}
	}

	public constructor(config: ScrollConfig<T>) {
		for (const key of <ScrollNames[]>Object.keys(config)) {
			const value = config[key];
			if (value !== undefined) {
				if (value instanceof WatchValue) {
					this[key].Bind(<WatchValue<any>>value);
				} else {
					this[key].Value = value;
				}
			}
		}
		this.Count.Add(null, () => { this.ViewChange(); });
		this.Length.Add(null, () => { this.ViewChange(); });
	}

	public JudgeIndex(index: number) {
		const lenth = this.Length.Value;
		return index >= 0 && index < lenth;
	}

	public ViewChange(): void {
		this.Progress = this.Progress;
		this.SelectIndex = undefined;
		this.SelectItem = undefined;
	}

	public EnsureVisible(_index?: number): void {
		const _progress = this.Progress;
		const row = this.Row.Value;
		const index = _index !== undefined ? _index : this.SelectIndex;
		if (index === undefined) { return; }
		const y = this.GetYByIndex(index);
		if (row > 2) {
			if (y < _progress + 1) {
				this.Progress = y - 1;
			} else if (y >= _progress + row - 1) {
				this.Progress = y - row + 2;
			}
		} else {
			if (y < _progress + 1) {
				this.Progress = y;
			} else if (y >= _progress + row - 1) {
				this.Progress = y - row + 1;
			}
		}
	}

	public GetXByPos(tx: number): number {
		return Math.floor((tx - this.Left.Value - this.Padding.Value) / this.ItemWidth.Value);
	}

	public GetYByPos(ty: number): number {
		return Math.floor((ty - this.Top.Value - this.Padding.Value) / this.ItemHeight.Value) + this.Progress;
	}

	public GetXByIndex(index: number): number {
		return index % this.Col.Value;
	}

	public GetYByIndex(index: number): number {
		return Math.floor(index / this.Col.Value);
	}

	public GetItemByPos(tx: number, ty: number): T | undefined {
		const progress = this.Progress;
		const x = this.GetXByPos(tx);
		const y = this.GetYByPos(ty);
		if (y >= progress && y < progress + this.Row.Value) {
			return this.GetItemByAzimuth(x, y);
		}
		return undefined;
	}

	public GetItemByAzimuth(x: number, y: number): T | undefined {
		const index = y * this.Col.Value + x;
		if (index >= 0 && index < this.Length.Value) {
			return this.List.Value[index];
		}
		return undefined;
	}

	public GetItemsSize(): ScrollItemInfo<T>[] {
		const list = this.List.Value;
		const progress = this.Progress;
		const col = this.Col.Value;
		const count = this.Count.Value;
		const start = progress * col;
		const viewList = list.slice(start, progress * col + count);

		const selectIndex = this.SelectIndex;
		const itemPadding = this.ItemPadding.Value;;
		const moveWidth = this.ItemWidth.Value;
		const moveHeight = this.ItemHeight.Value;

		const left = this.Left.Value + this.Padding.Value;
		const top = this.Top.Value + this.Padding.Value;
		const itemWidth = moveWidth;
		const itemHeight = moveHeight;

		const offsetWidth = itemWidth - itemPadding * 2;
		const offsetHeight = itemHeight - itemPadding * 2;

		let x = left;
		let y = top;
		let col_i = 0;

		return viewList.map((item, index) => {
			const isSelect = (start + index) === selectIndex;
			const offset = [x + itemPadding, y + itemPadding, offsetWidth, offsetHeight] as const;
			const position = [x, y, itemWidth, itemHeight] as const;
			const info = [item, isSelect, offset, position] as const;
			col_i++;
			if (col_i >= col) {
				col_i = 0;
				x = left;
				y += moveHeight;
			} else {
				x += moveWidth;
			}
			return info;
		});
	}

	public GetScrollSize(): [[number, number, number, number], [number, number, number, number]] | undefined {
		const row = this.Row.Value;
		const rowCount = this.RowCount.Value;
		if (rowCount <= row) { return undefined; }

		const progress = this.Progress;
		const left = this.Left.Value;
		const top = this.Top.Value;
		const width = this.Width.Value;
		const height = this.Height.Value;
		const scrollWidth = this.ScrollWidth.Value;
		const scrollHeight = this.ScrollHeight.Value;
		const scrollSpace = this.ScrollSpace.Value;

		const x = left + width - scrollWidth;
		const y = top;
		const w = scrollWidth;
		const h = height;

		const sx = x;
		const sy = y + progress * scrollSpace;
		const sw = w;
		const sh = scrollHeight;

		return [[x, y, w, h], [sx, sy, sw, sh]];
	}

	public SelectLast(loop?: boolean, count: number = 1): void {
		const length = this.Length.Value;
		const selectIndex = this.SelectIndex;
		if (selectIndex !== undefined && selectIndex - count >= 0) {
			this.SelectIndex = selectIndex - count;
		} else if (loop === true && length >= 0) {
			this.SelectIndex = length - 1;
		} else {
			this.SelectIndex = undefined;
		}
		this.EnsureVisible();
	}

	public SelectNext(loop?: boolean, count: number = 1): void {
		const length = this.Length.Value;
		const selectIndex = this.SelectIndex;
		if (selectIndex !== undefined && selectIndex + count < length) {
			this.SelectIndex = selectIndex + count;
		} else if (loop === true && length > 0) {
			this.SelectIndex = 0;
		} else {
			this.SelectIndex = undefined;
		}
		this.EnsureVisible();
	}

	public SelectLastRow(loop?: boolean): void {
		this.SelectLast(loop, this.Col.Value);
	}

	public SelectNextRow(loop?: boolean): void {
		this.SelectNext(loop, this.Col.Value);
	}

	public Swap(indexA: number, indexB: number): boolean {
		const { List: { Value: list } } = this;
		if (!this.JudgeIndex(indexA) || !this.JudgeIndex(indexB)) {
			return false;
		}
		const save = list[indexA];
		list[indexA] = list[indexB];
		list[indexB] = save;
		this.List.Value = [...list];
		return true;
	}
}
